#define _PVM_MEMORY_INTL_

#include "pvm/pvmapi.h"
#include "lib/pvmsystem.h"
#include "lib/memory/pvmmemory.h"
#include "lib/pvmassert.h"
#include "lib/pvmlog.h"

#include "lib/memory/dlmalloc.h"

#define ALLOC_HEAD_MARKER	0xc8c8c8c8
#define ALLOC_TAIL_MARKER	0xd6d6d6d6

//#define PVM_DEBUG_ALLOC
//#define PVM_DEBUG_ALLOC_ZERO_MEMORY
//#define PVM_DEBUG_ALLOC_LOG
//#define PVM_ALLOC_ASSERT_ON_NULL

struct PVM_MEMORY_DEBUG_INFO
{
	pvm_size	blockSize;
	pvm_int		id;
	pvm_int32	headMarker;
};

volatile static pvm_bool isPvmMmgrInitialized = false;
static IPVMMemoryManager* memoryManager = NULL;
volatile static pvm_int allocationId = 0;
static PVMCriticalSection* mmgrCritical;
static bool usingMmgrCritical = false;

extern PVMCriticalSection* PVMGetCriticalForMmgr();
extern void PVMFreeCriticalForMmgr();

class PVMMmgrSafeLock
{
private:
	PVMCriticalSection* criticalPtr;
public:
	PVMMmgrSafeLock(PVMCriticalSection* critical, bool useLock)
	{
		if(useLock)
			criticalPtr = critical;
		else
			criticalPtr = NULL;
		if(criticalPtr!=NULL)
			PVMEnterCritical(criticalPtr);
	}

	~PVMMmgrSafeLock()
	{
		if(criticalPtr!=NULL)
			PVMLeaveCritical(criticalPtr);
	}
};

#ifdef PVM_DEBUG_ALLOC

inline pvm_size PVMSetMemDebugFullInfoSize()
{
	return sizeof(PVM_MEMORY_DEBUG_INFO) + sizeof(pvm_int32);
}

inline pvm_size PVMSetMemDebugHeadInfoSize()
{
	return sizeof(PVM_MEMORY_DEBUG_INFO);
}

void PVMSetMemDebugInfoOnAllocation(void* memoryBlock, pvm_size reportedSize)
{
	PVM_MEMORY_DEBUG_INFO* debugInfo = (PVM_MEMORY_DEBUG_INFO*)memoryBlock;
	pvm_size tailMarkerOffset = PVMSetMemDebugHeadInfoSize() + reportedSize;
	pvm_int32* tailMarker = (pvm_int32*)(((char*)memoryBlock) + tailMarkerOffset);

	debugInfo->blockSize = reportedSize;
	debugInfo->id = allocationId++;
	debugInfo->headMarker = ALLOC_HEAD_MARKER;
	*tailMarker = ALLOC_TAIL_MARKER;

#ifdef PVM_DEBUG_ALLOC_ZERO_MEMORY
	void* memToZeroPtr = ((char*)memoryBlock) + PVMSetMemDebugHeadInfoSize();
	memset(memToZeroPtr, 0, reportedSize);
#endif

#ifdef PVM_DEBUG_ALLOC_LOG
	PVM_LOG(PVMLOG_COMPONENT_MEMORY, PVMLOG_EVENT_INFO, _PVM_STR("Allocation %d bytes, at addres %x (id %d)"), reportedSize, 0, debugInfo->id);
#endif
}

void PVMCheckMemDebugInfoOnDeallocation(void* reporetedPtr)
{
	if(!reporetedPtr)
		return;
	PVM_MEMORY_DEBUG_INFO* debugInfo = (PVM_MEMORY_DEBUG_INFO*)(((char*)reporetedPtr) - sizeof(PVM_MEMORY_DEBUG_INFO));
	pvm_int32* tailMarker = (pvm_int32*)(((char*)reporetedPtr) + debugInfo->blockSize);

#ifdef PVM_DEBUG_ALLOC_LOG
	PVM_LOG(PVMLOG_COMPONENT_MEMORY, PVMLOG_EVENT_INFO, _PVM_STR("Deallocation %d bytes, at addres %x (id %d)"), debugInfo->blockSize, (pvm_int)(reporetedPtr), debugInfo->id);
#endif

	pvm_assert(debugInfo->headMarker==ALLOC_HEAD_MARKER);
	pvm_assert(*tailMarker==ALLOC_TAIL_MARKER);
}
#endif

PVMAPI pvm_bool PVMCALL PVMMemoryInit(IPVMMemoryManager* customMemoryManager)
{
	if(customMemoryManager!=NULL)
	{
		if(customMemoryManager->Malloc==NULL || customMemoryManager->Free==NULL || customMemoryManager->Realloc==NULL || customMemoryManager->GetFlags==NULL)
			return false;
	}

	memoryManager = customMemoryManager;
	
	usingMmgrCritical = false;
	if(customMemoryManager && customMemoryManager->GetFlags() & PVM_MMGR_NO_THREAD_SYNC && PVMIsThreadSyncAvailable())
	{
		if((mmgrCritical = PVMGetCriticalForMmgr())==NULL)
			return false;

		usingMmgrCritical = true;
	}

	isPvmMmgrInitialized = true;

	return true;
}

PVMAPI void PVMCALL PVMMemoryDeinit()
{
	if(usingMmgrCritical)
		PVMFreeCriticalForMmgr();
}

PVMAPI pvm_bool PVMCALL PVMMemoryIsInitialized()
{
	return isPvmMmgrInitialized;
}

PVMAPI void* PVMCALL PVMMemoryAlloc(pvm_size size)
{
	if(!PVMMemoryIsInitialized())
		return NULL;

	pvm_size allocSize = size;

#ifdef PVM_DEBUG_ALLOC
	allocSize += PVMSetMemDebugFullInfoSize();
#endif

	void* result = NULL;
	if(memoryManager!=NULL)
	{
		PVMMmgrSafeLock(mmgrCritical, usingMmgrCritical);
		result = memoryManager->Malloc(memoryManager->param, allocSize);
	}
	else
		result = malloc(allocSize);

#ifdef PVM_DEBUG_ALLOC
	PVMSetMemDebugInfoOnAllocation(result, size);
	result = (char*)result + sizeof(PVM_MEMORY_DEBUG_INFO);
#endif

#ifdef PVM_ALLOC_ASSERT_ON_NULL
	pvm_assert(result!=NULL);
#endif

	return result;
}

PVMAPI void* PVMCALL PVMMemoryCalloc(pvm_size num, pvm_size size)
{
	pvm_size blockSize = num*size;
	void* ptr = PVMMemoryAlloc(blockSize);
	if(ptr!=NULL)
		memset(ptr, 0, blockSize);

	return ptr;
}

PVMAPI void PVMCALL PVMMemoryFree(void* ptr)
{
	if(!PVMMemoryIsInitialized() || ptr==NULL)
		return;
	void *freePtr = ptr;
#ifdef PVM_DEBUG_ALLOC
	freePtr = (char*)ptr - PVMSetMemDebugHeadInfoSize();
	//check if memory was not corrupted
	PVMCheckMemDebugInfoOnDeallocation(ptr);
#endif

	if(memoryManager!=NULL)
	{
		PVMMmgrSafeLock(mmgrCritical, usingMmgrCritical);
		memoryManager->Free(memoryManager->param, freePtr);
	}
	else
		free(freePtr);
}

PVMAPI void* PVMCALL PVMMemoryRealloc(void* ptr, pvm_size size)
{
	if(!PVMMemoryIsInitialized())
		return NULL;

	pvm_size allocSize = size;
	void *reallocPtr = ptr;

#ifdef PVM_DEBUG_ALLOC
	allocSize += sizeof(PVM_MEMORY_DEBUG_INFO) + sizeof(pvm_int32);
	reallocPtr = (char*)ptr - sizeof(PVM_MEMORY_DEBUG_INFO);
	PVMCheckMemDebugInfoOnDeallocation(ptr);
#endif
	
	void* result;
	if(memoryManager!=NULL)
	{
		PVMMmgrSafeLock(mmgrCritical, usingMmgrCritical);
		result = memoryManager->Realloc(memoryManager->param, reallocPtr, allocSize);
	}
	else
		result = realloc(reallocPtr, allocSize);

#ifdef PVM_DEBUG_ALLOC
	PVMSetMemDebugInfoOnAllocation(result, size);
	result = (char*)result + sizeof(PVM_MEMORY_DEBUG_INFO);
#endif

#ifdef PVM_ALLOC_ASSERT_ON_NULL
	pvm_assert(result!=NULL);
#endif

	return result;
}

void* PVMAllocableObject::operator new(size_t size)
{
	return PVMMemoryAlloc(size);
}

void* PVMAllocableObject::operator new[](size_t size)
{
	return PVMMemoryAlloc(size);
}

void PVMAllocableObject::operator delete(void *address)
{
	PVMMemoryFree(address);
}

void PVMAllocableObject::operator delete[](void *address)
{
	PVMMemoryFree(address);
}

//////////////////////////////////////////////////////////////////////////
// Default custom memory manager realization
//////////////////////////////////////////////////////////////////////////

void* PVMmmgrDlMalloc(void* param, pvm_size size)
{
	return mspace_malloc(param, size);
}

void PVMmmgrDlFree(void* param, void *ptr)
{
	mspace_free(param, ptr);
}

void* PVMmmgrDlRealloc(void* param, void *ptr, pvm_size size)
{
	return mspace_realloc(param, ptr, size);
}

int PVMmmgrDlGetFlags(void)
{
	return PVM_MMGR_NO_THREAD_SYNC;
}

void PVMmmgrDlDestory(void* mmgr)
{
	destroy_mspace(((IPVMMemoryManager*)mmgr)->param);
}

PVMAPI IPVMMemoryManager* PVMCALL PVMMemoryManagerCreate(TPvmMMGRType mmgrType, void* heapBuffer, pvm_size heapSize)
{
	switch(mmgrType)
	{
	case E_MMGR_DOUG_LEA_ALLOCATOR:
		{
			if(heapBuffer == NULL)
				return NULL;
			if(heapSize < sizeof(IPVMMemoryManager) + 128 * sizeof(size_t))
				return NULL;

			IPVMMemoryManager* mmgr;

			mmgr = (IPVMMemoryManager*)heapBuffer;
			memset(mmgr, 0, sizeof(IPVMMemoryManager));
			void* buffPtr = ((pvm_uint8*)heapBuffer) + sizeof(IPVMMemoryManager);

			mmgr->param = create_mspace_with_base(buffPtr, heapSize - sizeof(IPVMMemoryManager), true);
			if(mmgr->param==NULL)
				return NULL;
			mspace_track_large_chunks(mmgr->param, true);

			mmgr->Malloc = PVMmmgrDlMalloc;
			mmgr->Free = PVMmmgrDlFree;
			mmgr->Realloc = PVMmmgrDlRealloc;
			mmgr->GetFlags = PVMmmgrDlGetFlags;
			mmgr->Destory = PVMmmgrDlDestory;

			return mmgr;
		}
		break;
	default:
		return NULL;
	}
}

PVMAPI void PVMCALL PVMMemoryManagerDestroy(IPVMMemoryManager* mmgr)
{
	if(mmgr == NULL)
		return;
	if(mmgr->Destory == NULL)
		return;

	mmgr->Destory((_IPVMMemoryManager*)mmgr);
}